package site.yunnong.atvris.common.utils;

import lombok.extern.slf4j.Slf4j;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @Description 
 * 分布式id生成器 
 * 1. 采用snowflake算法，42位的时间前缀+10位的节点标识+12位的sequence避免并发的数字（12位不够用时强制得到新的时间前缀）
 * 2. 时间回拨后重试，尽可能降低回拨产生的影响 
 * 3. sequence取模板尽可能均匀 
 * 4. 机器IP作为workId(IP的最后网段，前3个网段不一样，可能导致workid重复)
 */
@Slf4j
public class IDGenerator {

    // 开始时间戳 2021-04-20 00:00:00
    private final static Long START_TIME_STAMP = 1618848000000L;
    // workId的范围是0~1023
    private final static Long WORK_ID_BIT = 10L;
    // 序列号的范围是0~4095
    private final static Long SEQUENCE_BIT = 12L;
    // 最大序列号 二进制 111111111111
    private final static Long MAX_SEQUENCE_BIT = ~(-1L << SEQUENCE_BIT);
    // workid左移12位
    private final static Long WORK_ID_LEFT_SHIFT = SEQUENCE_BIT;
    // 时间戳左移22位
    private final static Long TIME_STAMP_LEFT_SHIFT = SEQUENCE_BIT + WORK_ID_BIT;
    // 上一次时间戳
    private long lastTimeStamp = -1L;
    // 序列号
    private long sequence = 0L;
    // 默认为ip
    private final long workId;

    public IDGenerator() {
        workId = getIpForWorkId();
    }

    /**
     * 获取分布式id
     * @return
     */
    public synchronized Long nextId() {

        long currentTimeStamp = getCurrentTimeStamp();
        if (currentTimeStamp < lastTimeStamp) {
            // 当前时间戳小于上次时间戳，发生了时间回拨
            long offset = lastTimeStamp - currentTimeStamp;
            if (offset < 5) {
                try {
                    // 等待2倍时间
                    wait(offset << 1);
                    currentTimeStamp = getCurrentTimeStamp();
                    if (currentTimeStamp < lastTimeStamp) {
                        // 还是小于，抛异常
                        throw new RuntimeException("发生时间回拨,拒绝生成id");
                    }
                } catch (Exception e) {
                    log.error("获取当前时间戳出错", e);
                }

            } else {
                throw new RuntimeException("时间回拨大于5ms,拒绝生成id");
            }
        }

        if (currentTimeStamp == lastTimeStamp) {
            // 相同毫秒内，序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE_BIT;
            if (sequence == 0L) {
                // 相同毫秒内，序列号到大最大值
                // 当前时间戳移到下一毫秒
                currentTimeStamp = getNextMillisecond(lastTimeStamp);
            }
        }

        if (currentTimeStamp > lastTimeStamp) {
            // 当前时间戳大于上次时间戳
            sequence = 0L;
        }

        lastTimeStamp = currentTimeStamp;

        // 时间戳左移22位
        return ((currentTimeStamp - START_TIME_STAMP) << TIME_STAMP_LEFT_SHIFT)
                // workId左移12位
            | (workId << WORK_ID_LEFT_SHIFT)
                // 序列号
            | sequence; 
    }

    /**
     * 获取下一毫秒时间戳
     * @param lastTimeStamp
     * @return
     */
    private long getNextMillisecond(Long lastTimeStamp) {

        long currentTimeStamp = getCurrentTimeStamp();
        while (currentTimeStamp <= lastTimeStamp) {
            currentTimeStamp = getCurrentTimeStamp();
        }
        return currentTimeStamp;
    }

    /**
     * 获取当前时间戳
     * @return
     */
    private Long getCurrentTimeStamp() {
        return System.currentTimeMillis();
    }

    /**
     * 把IP当做workid(只计算最后一个网段，小于1023)
     * @return
     */
    public Long getIpForWorkId() {
        try {
            InetAddress addr = InetAddress.getLocalHost();
            byte[] ipAddr = addr.getAddress();
            return Long.parseLong(String.valueOf(ipAddr[3] & 0xFF)) & (~(-1 << 10));
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        IDGenerator generator = new IDGenerator();
        System.out.println(generator.nextId());
        long last = System.currentTimeMillis() - start;
        System.out.println(start);
    }
}